#include <ctype.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
// https://abf.io/import/cgilib
#include <cgi.h>
// https://abf.io/import/fcgi
#include <fcgios.h>
#include <fcgi_stdio.h>

// allow to redefine as gcc -D...
#ifndef DIR
#define DIR "/tmp"
#endif

#define HTTP_OK 200
#define HTTP_BAD_REQUEST 400
#define HTTP_ERROR 500

/* Here we could verify validity of a IPv4 or IPv6 address,
 * but that is very complex. Just sanitize input. */
int
_verify_ip(char *ip){
	int rc = 0;
	for (int i = 0; i < strlen(ip); i++){
		if (!( (ip[i] == '.') || /* IPv4 dots */
			   (ip[i] == ':') || /* IPv6 */
			   (ip[i] == '/') || /* XXX Is it needed for IPv6? */
			   (isalnum(ip[i]) != 0) /* Numbers (v4 and v6) and letters (v6) */
		   )) {
			rc = 1;
			break;
		}
	}
	return rc;
}

int
_verify_geometry(char *value){
	int rc = 0;
	for (int i = 0; i < strlen(value); i++){
		if (isdigit(value[i]) == 0) {
			rc = 1;
			break;
		}
	}
	return rc;
}

/* Generate a random alphanum sequence
 * Example: 10841ee0cc4f (12 chars) */
int
_rand(char **rs){
	// https://books.google.ru/books?id=1nG3BwAAQBAJ
	// hex.data will be 6x2=12 chars
	unsigned char buf_rnd[6];
	gnutls_datum_t rnd = { buf_rnd, sizeof(buf_rnd) };
	int rc;
	rc = gnutls_rnd(GNUTLS_RND_NONCE, rnd.data, rnd.size);
	if (rc != GNUTLS_E_SUCCESS) {
		fprintf(stderr, "Error in gnutls_rnd()\n");
		goto out;
	}
	gnutls_datum_t hex;
	rc = gnutls_hex_encode2(&rnd, &hex);
	if (rc != GNUTLS_E_SUCCESS) {
		fprintf(stderr, "Error in gnutls_hex_encode2()\n");
		goto out;
	}
	*rs = malloc(hex.size);
	rc = sprintf(*rs, "%s", hex.data);
	if (rc < 0)  {
		fprintf(stderr, "Error in sprintf()\n");
		goto out;
	}
out:
	return rc;
}

int
main(){
	int rc = 0;
	int http_code = HTTP_ERROR;
	// text of errors which must be seen by people
	char *error_msg;
	if (access(DIR, W_OK) != 0) {
		fprintf(stderr, "Directory %s does not exist or is not writable\n", DIR);
		goto out;
	}
	// first chdir() and then chroot()!
	if (!( chdir(DIR) == 0 && chroot(DIR) == 0 )) {
		fprintf(stderr, "Cannot chdir and chroot into %s\n", DIR);
		goto out;
	}
	s_cgi *cgi;
	while(FCGI_Accept() >= 0) {
		// cgiReadVariables() returns NULL if QUESRY_STRING is empty,
		// and it is empty when neither width not height are defined,
		// so checking its return value is useless.
		cgi = cgiInit();
		char *ip = getenv("REMOTE_ADDR");
		if (ip == NULL) {
			fprintf(stderr, "Env REMOTE_ADDR is not set\n");
			http_code = HTTP_ERROR;
			goto fcgi_out;
		}
		rc = _verify_ip(ip);
		if (rc != 0) {
			fprintf(stderr, "Incorrect REMOTE_ADDR\n");
			http_code = HTTP_BAD_REQUEST;
			goto fcgi_out;
		}
		char *cgi_width = cgiGetValue(cgi, "width");
		char *cgi_height = cgiGetValue(cgi, "height");
		int bal = 0;
		if ( (cgi_width != NULL) && (_verify_geometry(cgi_width) == 0) )
			bal++;
		if ( (cgi_height != NULL) && (_verify_geometry(cgi_height) == 0) )
			bal++;
		if (bal == 1) {
			error_msg = "Define both width and height";
			http_code = HTTP_BAD_REQUEST;
			goto fcgi_out;
		}
		char *hex;
		int max_try = 10;
		for (int i = 1; i <= max_try; i++) {
			if (i == max_try) {
				fprintf(stderr, "Error in random file loop\n");
				http_code = HTTP_ERROR;
				goto fcgi_out;
			}
			rc = _rand(&hex);
			if (rc < 0) {
				fprintf(stderr, "Error in _rand()\n");
				continue;
			}
			if (access(hex, F_OK) != 0) {
				break;
			}
		}
		FCGI_FILE *d = fopen(hex, "w");
		if (d == NULL) {
			fprintf(stderr, "Error openning file %s/%s for writing\n", DIR, hex);
			http_code = HTTP_ERROR;
			goto fcgi_out;
		}
		fprintf(d, "ip=%s\n", ip);
		if (bal == 2) {
			fprintf(d, "width=%s\n", cgi_width);
			fprintf(d, "height=%s\n", cgi_height);
		}
		rc = fclose(d);
		if (rc != 0) {
			fprintf(stderr, "Error closing file %s/%s\n", DIR, hex);
			http_code = HTTP_ERROR;
			goto fcgi_out;
		}
		http_code = HTTP_OK;
fcgi_out:
		cgiFree(cgi);
		printf("Content-Type: text/plain; charset=utf-8\r\n");
		printf("Status: %d\r\n", http_code);
		printf("\r\n", http_code);
		if (http_code == HTTP_OK) {
			printf("OK (%s)\r\n", hex);
			rc = 0;
		} else {
			if (error_msg)
				printf("ERROR: %s\r\n", error_msg);
			rc = 1;
		}
	}
out:
	return rc;
}
